home *** CD-ROM | disk | FTP | other *** search
/ Mac Easy 2010 May / Mac Life Ubuntu.iso / casper / filesystem.squashfs / usr / share / pyshared / computerjanitorapp / ui_gtk.py < prev    next >
Encoding:
Python Source  |  2009-04-17  |  13.0 KB  |  375 lines

  1. # ui_gtk.py - graphical user interface implemented using GTK+
  2. # Copyright (C) 2008, 2009  Canonical, Ltd.
  3. #
  4. # This program is free software: you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation, version 3 of the License.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program.  If not, see <http://www.gnu.org/licenses/>.
  15.  
  16.  
  17. import logging
  18. import os
  19. import sys
  20. import threading
  21. import time
  22. import traceback
  23.  
  24. import computerjanitor
  25. import computerjanitorapp
  26. _ = computerjanitorapp.setup_gettext()
  27.  
  28. # We can't import gtk here, since that would mean it gets imported
  29. # even when we run in command line mode. Thus, we do the imports in
  30. # the class, when we know we need them.
  31.  
  32.  
  33. import os
  34. GLADE = os.environ.get("COMPUTER_JANITOR_GLADE", 
  35.                        "/usr/share/computer-janitor/ComputerJanitor.glade")
  36.  
  37.  
  38. NAME_COL = 0
  39. STATE_COL = 1
  40. CRUFT_COL = 2
  41.  
  42.  
  43. class ListUpdater(threading.Thread):
  44.  
  45.     def __init__(self, ui):
  46.         threading.Thread.__init__(self)
  47.         self.ui = ui
  48.  
  49.     def run(self):
  50.         crufts = self.find_cruft()
  51.         crufts = self.sort_cruft(crufts)
  52.         self.ui.do(self.ui.update_progressbar, "", 0.0)
  53.         self.ui.do(self.ui.update_cruft_list, crufts)
  54.  
  55.     def plugin_find_cb(self, filename, index, count):
  56.         self.ui.do(self.ui.update_progressbar, _("Finding plugins"), 
  57.                    float(index+1) / count)
  58.  
  59.     def find_cruft(self):
  60.         list = []
  61.         plugins = self.ui.pm.get_plugins(callback=self.plugin_find_cb)
  62.         for i in range(len(plugins)):
  63.             self.ui.do(self.ui.update_progressbar, _("Finding problems"), 
  64.                        float(i+1) / len(plugins))
  65.  
  66.             for cruft in plugins[i].get_cruft():
  67.                 list.append(cruft)
  68.         
  69.         return self.ui.app.remove_whitelisted(list)
  70.  
  71.     def sort_cruft(self, crufts):
  72.         # Sort crufts: First all unignored cruft, then all ignored cruft.
  73.         # In each section first everything else, then files to be removed,
  74.         # then packages to be removed.
  75.         
  76.         def key(cruft):
  77.             name = cruft.get_name()
  78.             if isinstance(cruft, computerjanitor.PackageCruft):
  79.                 kind_class = 3
  80.             elif isinstance(cruft, computerjanitor.FileCruft):
  81.                 kind_class = 2
  82.             else:
  83.                 kind_class = 1
  84.             return (not self.ui.app.state.is_enabled(name),
  85.                     kind_class, name)
  86.  
  87.         list = [(key(cruft), cruft) for cruft in crufts]
  88.         list.sort()
  89.         return [cruft for k, cruft in list]
  90.  
  91.  
  92. class Cleaner(threading.Thread):
  93.  
  94.     def __init__(self, ui, crufts, plugins):
  95.         threading.Thread.__init__(self)
  96.         self.ui = ui
  97.         self.crufts = crufts
  98.         self.plugins = plugins
  99.  
  100.     def run(self):
  101.         for cruft in self.crufts:
  102.             self.ui.do(self.ui.pulse_progressbar, _("Cleaning up"))
  103.             name = cruft.get_name()
  104.             if self.ui.app.state.is_enabled(name):
  105.                 if self.ui.options.no_act:
  106.                     logging.info(_("Pretending to clean up: %s") % name)
  107.                 else:
  108.                     logging.info(_("Cleaning up: %s") % name)
  109.                     cruft.cleanup()
  110.  
  111.         for plugin in self.plugins:
  112.             self.ui.do(self.ui.pulse_progressbar, _("Post-cleanup"))
  113.             if self.ui.options.no_act:
  114.                 logging.info(_("Pretending to post-cleanup: %s") % plugin)
  115.             else:
  116.                 logging.info(_("Post-cleanup: %s") % plugin)
  117.                 error = None
  118.                 try:
  119.                     plugin.post_cleanup()
  120.                 except Exception, e:
  121.                     logging.debug(unicode(traceback.format_exc()))
  122.                     self.ui.do(self.ui.idle_error_message, 
  123.                                _("Could not clean up properly"),
  124.                                unicode(e))
  125.                     break
  126.                 
  127.         self.ui.show_cruft()
  128.         self.ui.do(self.ui.button_cleanup.set_sensitive, True)
  129.  
  130.  
  131. class GtkUserInterface(computerjanitorapp.UserInterface):
  132.  
  133.     def update_progressbar(self, msg, fraction):
  134.         self.progressbar.set_text(msg)
  135.         self.progressbar.set_fraction(fraction)
  136.  
  137.     def pulse_progressbar(self, msg):
  138.         self.progressbar.set_text(msg)
  139.         self.progressbar.pulse()
  140.  
  141.     def update_sensitive(self):
  142.         sensitive = False
  143.         iter = self.liststore.get_iter_first()
  144.         while not sensitive and iter:
  145.             sensitive = self.liststore.get_value(iter, STATE_COL)
  146.             iter = self.liststore.iter_next(iter)
  147.             
  148.         self.button_cleanup.set_sensitive(sensitive)
  149.  
  150.     def show_cruft(self, *args):
  151.         updater = ListUpdater(self)
  152.         updater.start()
  153.  
  154.     def cleanup(self, *args):
  155.         self.button_cleanup.set_sensitive(False)
  156.         
  157.         crufts = []
  158.         iter = self.liststore.get_iter_first()
  159.         while iter:
  160.             crufts.append(self.liststore.get_value(iter, CRUFT_COL))
  161.             iter = self.liststore.iter_next(iter)
  162.         
  163.         packages = [cruft 
  164.                     for cruft in crufts 
  165.                     if (isinstance(cruft, computerjanitor.PackageCruft) and
  166.                         self.app.state.is_enabled(cruft.get_name()))]
  167.  
  168.         msg = _("Really clean up?")
  169.         dialog = self.gtk.MessageDialog(parent=self.window_main,
  170.                                         type=self.gtk.MESSAGE_WARNING,
  171.                                         buttons=self.gtk.BUTTONS_YES_NO,
  172.                                         message_format=msg)
  173.  
  174.         if packages:
  175.             msg = (_("You are <b>removing %d .deb packages.</b> "
  176.                      "This may break your system, if you need them. "
  177.                      "Do you want to continue?") % 
  178.                    len(packages))
  179.         else:
  180.             msg = _("Do you want to continue?")
  181.         dialog.format_secondary_markup(msg)
  182.  
  183.         dialog.show_all()
  184.         response = dialog.run()
  185.         dialog.hide()
  186.         if response != self.gtk.RESPONSE_YES:
  187.             self.update_sensitive()
  188.             return
  189.  
  190.         cleaner = Cleaner(self, crufts, self.pm.get_plugins())
  191.         cleaner.start()
  192.  
  193.     def close(self, *args):
  194.         self.gtk.main_quit()
  195.  
  196.     def cruft_toggled(self, toggle, path):
  197.         iter = self.liststore.get_iter(path)
  198.  
  199.         cruft = self.liststore.get_value(iter, CRUFT_COL)
  200.         cruft_name = cruft.get_name()
  201.         enabled = self.liststore.get_value(iter, STATE_COL)
  202.  
  203.         enabled = not enabled
  204.         if enabled:
  205.             self.app.state.enable(cruft_name)
  206.         else:
  207.             self.app.state.disable(cruft_name)
  208.         self.app.state.save(self.options.state_file)
  209.         self.liststore.set_value(iter, STATE_COL, enabled)
  210.         
  211.         self.update_sensitive()
  212.  
  213.     def translate_description(self, cruft):        
  214.         # Ugly hack to get translations to work; see LP: #361312.
  215.         desc = cruft.get_description()
  216.         import computerjanitor
  217.         if isinstance(cruft, computerjanitor.PackageCruft):
  218.             desc1, desc2 = desc.split("\n\n", 2)
  219.             if desc1 and desc2:
  220.                 desc = "%s\n\n%s" % (_(desc1), desc2)
  221.         elif isinstance(cruft, computerjanitor.FileCruft):
  222.             if desc.endswith("\n"):
  223.                 desc = desc[:-1]
  224.         return desc
  225.  
  226.     def cursor_changed(self, treeview):
  227.         selection = treeview.get_selection()
  228.         model, rows = selection.get_selected_rows()
  229.         if len(rows) == 1:
  230.             iter = model.get_iter(rows[0])
  231.             cruft = model.get_value(iter, CRUFT_COL)
  232.             desc = self.translate_description(cruft)
  233.             self.cruft_description.set_label(desc)
  234.             self.do(self.scroll_to_selection)
  235.  
  236.     def scroll_to_selection(self):
  237.         selection = self.treeview_cruft.get_selection()
  238.         model, rows = selection.get_selected_rows()
  239.         if len(rows) == 1:
  240.             iter = model.get_iter(rows[0])
  241.             path = model.get_path(iter)
  242.             self.treeview_cruft.scroll_to_cell(path)
  243.         
  244.     def mapped(self, *args):
  245.         if os.getuid() != 0:
  246.             dialog = self.error_dialog(_("Root access required."), 
  247.                                        _("You must run computer-janitor-gtk "
  248.                                          "as root. Sorry."))
  249.             dialog.show()
  250.             dialog.run()
  251.             sys.exit(1)
  252.  
  253.         try:
  254.             self.app.verify_apt_cache()
  255.         except computerjanitor.Exception, e:
  256.             logging.debug(unicode(traceback.format_exc()))
  257.             dialog = self.error_dialog(str(e))
  258.             dialog.show()
  259.             dialog.run()
  260.             sys.exit(1)
  261.  
  262.         self.show_cruft()
  263.         self.window_main.disconnect(self.mapped_id)
  264.  
  265.     def format_name(self, cruft):
  266.         name = self.gobject.markup_escape_text(cruft.get_shortname())
  267.         desc = cruft.get_prefix_description() or cruft.get_prefix()
  268.         desc = self.gobject.markup_escape_text(desc)
  269.         return ("<b>%s</b> (%s)" % (name, desc))
  270.  
  271.     def update_cruft_list(self, crufts):
  272.         self.liststore.clear()
  273.         self.cruft_description.set_text("")
  274.         for cruft in crufts:
  275.             dict = {
  276.                 NAME_COL: self.format_name(cruft),
  277.                 STATE_COL: self.app.state.is_enabled(cruft.get_name()),
  278.                 CRUFT_COL: cruft,
  279.             }
  280.             list = [dict[i] for i in sorted(dict.keys())]
  281.             self.liststore.append(list)
  282.         self.update_sensitive()
  283.  
  284.     def error_dialog(self, msg, secondary_msg=None):
  285.         dialog = self.gtk.MessageDialog(parent=self.window_main,
  286.                                         type=self.gtk.MESSAGE_ERROR,
  287.                                         buttons=self.gtk.BUTTONS_OK,
  288.                                         message_format=msg)
  289.         if secondary_msg:
  290.             dialog.format_secondary_text(secondary_msg)
  291.  
  292.         return dialog
  293.  
  294.     def idle_error_message(self, msg, secondary_msg=None):
  295.         dialog = self.error_dialog(msg, secondary_msg)
  296.         dialog.connect("response", lambda d=dialog, *args: d.destroy())
  297.         dialog.show()
  298.  
  299.     def do(self, callable, *args):
  300.         def helper():
  301.             callable(*args)
  302.             return False
  303.         self.gobject.idle_add(helper)
  304.  
  305.     def run(self, options, args):
  306.         import gtk
  307.         import gtk.glade
  308.         import gobject
  309.         
  310.         locpath = os.environ.get("LOCPATH", "/usr/share/locale")
  311.         gtk.glade.bindtextdomain("computerjanitor", locpath)
  312.         gtk.glade.textdomain("computerjanitor")
  313.  
  314.         self.options = options
  315.         self.gtk = gtk
  316.         self.gobject = gobject
  317.         
  318.         self.app.state.load(options.state_file)
  319.  
  320.         self.glade = gtk.glade.XML(GLADE)
  321.         self.window_main = self.glade.get_widget("window_main")
  322.         self.treeview_cruft = self.glade.get_widget("treeview_cruft")
  323.         self.cruft_description = self.glade.get_widget("cruft_description")
  324.         self.button_cleanup = self.glade.get_widget("button_cleanup")
  325.         
  326.         self.mapped_done = False
  327.         
  328.         # connect to signals (we do this manually for now)
  329.         todo = [
  330.             ("button_cleanup", "clicked", self.cleanup, None),
  331.             ("button_close", "clicked", self.close, None),
  332.             ("window_main", "delete-event", self.close, None),
  333.             ("window_main", "map-event", self.mapped, "mapped_id"),
  334.             ("treeview_cruft", "cursor-changed", self.cursor_changed, None),
  335.         ]
  336.         for widget_name, signal_name, callback, attr in todo:
  337.             widget = self.glade.get_widget(widget_name)
  338.             id = widget.connect(signal_name, callback)
  339.             if attr:
  340.                 setattr(self, attr, id)
  341.  
  342.         # create the column with the toggle
  343.         col = gtk.TreeViewColumn(_('Remove/fix?'))
  344.         cr = gtk.CellRendererToggle()
  345.         cr.connect("toggled", self.cruft_toggled)
  346.         col.pack_start(cr, expand=False)
  347.         col.add_attribute(cr, 'active', STATE_COL)
  348.         self.treeview_cruft.append_column(col)
  349.         self.treeview_cruft.set_headers_visible(True)
  350.  
  351.         # create the column with the name of cruft
  352.         col = gtk.TreeViewColumn(_('Name'))
  353.         cr = gtk.CellRendererText()
  354.         col.pack_start(cr)
  355.         col.add_attribute(cr, 'markup', NAME_COL)
  356.         self.treeview_cruft.append_column(col)
  357.         
  358.         # create the store
  359.         self.liststore = gtk.ListStore(str, 'gboolean', object)
  360.  
  361.         # attach it to the view
  362.         self.treeview_cruft.set_model(self.liststore)
  363.         
  364.         # setup progress bar
  365.         self.progressbar = self.glade.get_widget("progressbar")
  366.         
  367.         # Display the version number.
  368.         self.cruft_description.set_text(_("Computer Janitor %s") % 
  369.                                         computerjanitorapp.VERSION)
  370.  
  371.         # show the window
  372.         self.window_main.show()
  373.         gtk.gdk.threads_init()
  374.         gtk.main()
  375.